#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>

#include<iostream>
#include<vector>
#include<string>
#include<fstream>
#include <sstream> 
#include<random> 
using namespace std;

class CreateInput{
public:

    CreateInput(){
        for(char c = 'a'; c <= 'z'; c++){
            chars.push_back(c);
        }
        for(char c = 'A'; c <= 'A'; c++){
            chars.push_back(c);
        }
    }


    ~CreateInput(){
        while (!stdin_format.empty()) {
            stdin_format.erase(stdin_format.begin());
        }
        while (!chars.empty()) {
            chars.erase(chars.begin());
        }
        while (!input_file.empty()) {
            input_file.erase(input_file.begin());
        }
    }

    void travel_dir(const char* path){
        DIR *d;
        struct dirent* file = new dirent();
        struct stat* st = new struct stat();
        if(!(d = opendir(path))){ // 打不开指定的起始文件夹
            printf("erroe dirs %s!!!\n", path);
            return;
        }

        while((file = readdir(d)) != NULL){
            // 跳过隐藏文件, 当前目录(.),或上一级目录
            if(strncmp(file->d_name, ".", 1) == 0)
                continue;
            //如果是普通的文件
            if(file->d_type == 8){
                char* file_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                char* input_file_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);

                memset(file_path, '\0', sizeof(file_path));
                memset(input_file_path, '\0', sizeof(input_file_path));

                strcat(file_path, path);
                strcat(input_file_path, path);

                strcat(file_path, "/");
                strcat(input_file_path, "/input.txt");

                strcat(file_path, file->d_name);

                if(strncmp(file->d_name, "stdin_format.txt", 16) == 0){
                    this->stdin_format.push_back(file_path);
                    this->input_file.push_back(input_file_path);
                }
            }
            //如果是文件夹
            if(file->d_type == 4){
                char* dir_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                memset(dir_path, '\0', sizeof(dir_path));
                strcat(dir_path, path);
                strcat(dir_path, "/");
                strcat(dir_path, file->d_name);
                travel_dir(dir_path);
            }
        }
        closedir(d);
        return;
    }

    void generateInput() {
        string info = "\0";
        string number = "";
        ifstream ifs_stdin_format;
        ofstream ofs_input;
	    srand((unsigned)time(NULL));

        for(int index = 0; index < stdin_format.size(); index++){

            ifs_stdin_format.open(stdin_format[index], ios::in);
            if (!ifs_stdin_format.is_open()){
                std::cout << "++++stdin_format.txt open fail++++" << endl;
                continue;
            }

            ofs_input.open(input_file[index], ios::out | ios::trunc);
            if (!ofs_input.is_open()){
                cout << "++++input.txt open fail++++" << endl;
                continue;
            }


            while(getline(ifs_stdin_format, info, ' ')){
                if(info.length() >= 3 && strncmp(info.substr(0,3).c_str(), "int", 3) == 0){
                    int start = 0, end = 0;

                    number = "";
                    int i = 4;
                    for(; info[i] != ','; i++)
                        number += info[i];
                    start = std::atoi(number.c_str());
                    i++;

                    number = "";
                    for(; info[i] != ')'; i++)
                        number += info[i];
                    end = std::atoi(number.c_str());
                    ofs_input << (rand()%(end-start+1))+start << ' ';
                }
                else if(info.length() >= 4 && strncmp(info.substr(0,4).c_str(), "char", 4) == 0){
                    ofs_input << chars[rand()%(chars.size())]<< ' ';
                }
                else if(info.length() >= 6 && strncmp(info.substr(0,6).c_str(), "string", 6) == 0){
                    int start = 0, end = 0;

                    number = "";
                    int i = 7;
                    for(; info[i] != ','; i++)
                        number += info[i];
                    start = std::atoi(number.c_str());
                    i++;

                    number = "";
                    for(; info[i] != ')'; i++)
                        number += info[i];
                    end = std::atoi(number.c_str());

                    int lengh = (rand()%(end-start+1))+start;
                    for(int j = 0; j < lengh; j++)
                        ofs_input << chars[rand()%(chars.size())];
                    ofs_input << ' ';
                }
            }
        
            ifs_stdin_format.close();
            ofs_input.close();
        }
    }

private:
    vector<char> chars;
    vector<string> stdin_format;
    vector<string> input_file;
};

class Execute{

public:

    Execute(){}

    ~Execute(){
        while (!shell_command.empty()) {
            shell_command.erase(shell_command.begin());
        }
    }

    void travel_dir(const char* path){
        DIR *d;
        struct dirent* file = new dirent();
        struct stat* st = new struct stat();
        if(!(d = opendir(path))){ // 打不开指定的起始文件夹
            printf("erroe dirs %s!!!\n", path);
            return;
        }

        while((file = readdir(d)) != NULL){
            // 跳过隐藏文件, 当前目录(.),或上一级目录
            if(strncmp(file->d_name, ".", 1) == 0)
                continue;
            //如果是普通的文件
            if(file->d_type == 8){
                char* dir_prefix = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                memset(dir_prefix, '\0', sizeof(dir_prefix));
                strcat(dir_prefix, path);
                strcat(dir_prefix, "/");

                string file_name = file->d_name;
                if(strncmp(file_name.substr(file_name.length()-3,file_name.length()).c_str(), \
                           "cpp", 3) == 0){

                    file_name = file_name.substr(0, file_name.length()-4);

                    //shell_command.push_back("cd " + string(dir_prefix));
                    shell_command.push_back(string(file_name));
                    shell_command.push_back(string(dir_prefix));
                    shell_command.push_back("g++ " + file_name + ".cpp -o " + file_name + ".out");
                    shell_command.push_back("./" + file_name + ".out <input.txt >" + file_name + ".txt");
                    //shell_command.push_back("cd ../..");

                }
            }
            //如果是文件夹
            if(file->d_type == 4){
                char* dir_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                memset(dir_path, '\0', sizeof(dir_path));
                strcat(dir_path, path);
                strcat(dir_path, "/");
                strcat(dir_path, file->d_name);
                travel_dir(dir_path);
            }
        }
        closedir(d);
        return;
    }


    void execute(){
        for(vector<string>::iterator shell_commandIt = shell_command.begin(); shell_commandIt != shell_command.end();){

            string file_name = *shell_commandIt;
            shell_commandIt++;

            chdir((*shell_commandIt).c_str());
            shell_commandIt++;

            system((*shell_commandIt).c_str());
            shell_commandIt++;

            int status = system((*shell_commandIt).c_str());
            shell_commandIt++;
            if (-1 == status || !WIFEXITED(status) || WEXITSTATUS(status)){
                system(("rm -f " + file_name + ".txt").c_str());
            }
            
            chdir("../..");
        } 
    }

private:
    vector<string> shell_command;
    
};

class Judge{

public:

    vector<vector<string>> output_file;


    Judge(){
        first_in_dir = true;
    }



    ~Judge(){
        while (!output_file.empty()){
            while(!(*output_file.begin()).empty())
                (*output_file.begin()).erase((*output_file.begin()).begin());
            output_file.erase(output_file.begin());
        }
    }



    void travel_dir(const char* path){
        DIR *d;
        struct dirent* file = new dirent();
        struct stat* st = new struct stat();
        if(!(d = opendir(path))){ // 打不开指定的起始文件夹
            printf("erroe dirs %s!!!\n", path);
            return;
        }

        while((file = readdir(d)) != NULL){
            // 跳过隐藏文件, 当前目录(.),或上一级目录
            if(strncmp(file->d_name, ".", 1) == 0)
                continue;
            //如果是普通的文件
            if(file->d_type == 8){

                string file_name = file->d_name;

                if(strncmp(file_name.substr(file_name.length()-3,file_name.length()).c_str(), \
                           "cpp", 3) == 0){
                    
                    file_name = file_name.substr(0, file_name.length()-4);
                    
                    char* output_file_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                    memset(output_file_path, '\0', sizeof(output_file_path));
                    strcat(output_file_path, path);
                    strcat(output_file_path, "/");
                    strcat(output_file_path, file_name.c_str());

                    std::ifstream ifs_check;
                    ifs_check.open(string(output_file_path)+".txt", std::ios::in);
                    if (ifs_check.is_open()){
                        if(first_in_dir){
                            vector<string> temp;
                            output_file.push_back(temp);
                            first_in_dir = false;
                        }
                        output_file.back().push_back(output_file_path);
                    }
                }
            }
            //如果是文件夹
            if(file->d_type == 4){
                char* dir_path = (char *)malloc(strlen(path) + strlen(file->d_name) + 2);
                memset(dir_path, '\0', sizeof(dir_path));
                strcat(dir_path, path);
                strcat(dir_path, "/");
                strcat(dir_path, file->d_name);
                first_in_dir = true;
                travel_dir(dir_path);
            }
        }
        closedir(d);
        return;
    }



    vector<vector<vector<bool>>> judge(){
        vector<vector<vector<bool>>> judge_result;
        for(vector<vector<string>>::iterator dirIt = output_file.begin(); dirIt != output_file.end(); dirIt++){
            vector<vector<bool>> dir_judge;
            judge_result.push_back(dir_judge);
            for(vector<string>::iterator output_fileIt1 = (*dirIt).begin(); output_fileIt1 != (*dirIt).end(); output_fileIt1++){
                vector<bool> file1;
                judge_result.back().push_back(file1);
                for(vector<string>::iterator output_fileIt2 = (*dirIt).begin(); output_fileIt2 != (*dirIt).end(); output_fileIt2++){

                    std::ifstream in1((*output_fileIt1)+".txt");
                    std::ifstream in2((*output_fileIt2)+".txt");
                    std::stringstream  tmp1;
                    std::stringstream  tmp2;

                    tmp1 << in1.rdbuf();
                    tmp2 << in2.rdbuf();

                    std::string str1 = tmp1.str();
                    std::string str2 = tmp2.str();

                    judge_result.back().back().push_back(strcmp(str1.c_str(), str2.c_str()) == 0);

                }
            }
        }
        return judge_result;
    }
private:
    bool first_in_dir; 
};



int main(int argc,char *argv[]){
    if(argc != 3)
     return 0;

    ofstream ofs_equal;
    ofstream ofs_inequal;
    ofs_equal.open("./output/equal.csv",ios::out|ios::trunc);
    ofs_inequal.open("./output/inequal.csv",ios::out|ios::trunc);

    if(!ofs_equal.is_open() || !ofs_inequal.is_open())
        return 0;
    
    int count = std::atoi(argv[2]);

    vector<vector<vector<bool>>> judge_result;
    CreateInput *input = NULL;
    Execute *execute = NULL;
    Judge *judge = NULL;

    input = new CreateInput();
    execute = new Execute();
    judge = new Judge();
    input->travel_dir(argv[1]);
    input->generateInput();
    execute->travel_dir(argv[1]);
    execute->execute();
    judge->travel_dir(argv[1]);
    judge_result = judge->judge();
    delete input;
    delete execute;
    delete judge;

    vector<vector<vector<bool>>> judge_temp;

    if(count >= 3){
        for(int i = 0; i < count-2; i++){
            input = new CreateInput();
            execute = new Execute();
            judge = new Judge();
            input->travel_dir(argv[1]);
            input->generateInput();
            execute->travel_dir(argv[1]);
            execute->execute();
            judge->travel_dir(argv[1]);
            judge_temp = judge->judge();

            for(int i = 0; i < judge_result.size(); i++){
                for(int j = 0; j < judge_result[i].size(); j++){
                    for(int k = 0; k < judge_result[i][j].size(); k++){
                        judge_result[i][j][k] = judge_result[i][j][k] && judge_temp[i][j][k];
                    }
                }
            }

            delete input;
            delete execute;
            delete judge;
        }
    }

    input = new CreateInput();
    execute = new Execute();
    judge = new Judge();
    input->travel_dir(argv[1]);
    input->generateInput();
    execute->travel_dir(argv[1]);
    execute->execute();
    judge->travel_dir(argv[1]);
    judge_temp = judge->judge();

    for(int i = 0; i < judge_result.size(); i++){
        for(int j = 0; j < judge_result[i].size(); j++){
            for(int k = 0; k < judge_result[i][j].size(); k++){
                judge_result[i][j][k] = judge_result[i][j][k] && judge_temp[i][j][k];
            }
        }
    }

    
    for(int i = 0; i < judge_result.size(); i++){
        for(int j = 0; j < judge_result[i].size(); j++){
            for(int k = j+1; k < judge_result[i][j].size(); k++){
                string file1 = judge->output_file[i][j];
                string file2 = judge->output_file[i][k];
                if(judge_result[i][j][k])
                    ofs_equal << file1.substr(1,file1.length()) << ".cpp" << "," \
                              << file2.substr(1,file2.length()) << ".cpp" << std::endl;
                else
                    ofs_inequal << file1.substr(1,file1.length()) << ".cpp" << "," \
                                << file2.substr(1,file2.length()) << ".cpp" << std::endl;
            }
        }
    }

    ofs_equal.close();
    ofs_inequal.close();

    delete input;
    delete execute;
    delete judge;
}